/* $Id: os_timestamp_posix.c 3553 2011-05-05 06:14:19Z nanang $ */
/* 
 * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com)
 * Copyright (C) 2003-2008 Benny Prijono <benny@prijono.org>
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA 
 */


 #include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <ctype.h>
#include <dpl/os.h>
#include	<dpl/config.h>
#include <dpl/log.h>
D_LOG_DOMAIN(os_timestamp_posix,"this is a case, how to use");

#define U32MAX  (0xFFFFFFFFUL)
#define NANOSEC (1000000000UL)
#define USEC    (1000000UL)
#define MSEC    (1000)

#define u64tohighprec(u64)	((dpl_highprec_t)((dpl_int64_t)(u64)))


#if defined(DPL_HAS_INT64) && DPL_HAS_INT64 != 0
   typedef dpl_int64_t dpl_highprec_t;
#else
	 typedef dpl_int32_t dpl_highprec_t;
#endif

/**
 * @def dpl_highprec_mul
 * dpl_highprec_mul(a1, a2) - High Precision Multiplication
 * Multiply a1 and a2, and store the result in a1.
 */
#ifndef dpl_highprec_mul
#   define dpl_highprec_mul(a1,a2)   (a1 = a1 * a2)
#endif

/**
 * @def dpl_highprec_div
 * dpl_highprec_div(a1, a2) - High Precision Division
 * Divide a2 from a1, and store the result in a1.
 */
#ifndef dpl_highprec_div
#   define dpl_highprec_div(a1,a2)   (a1 = a1 / a2)
#endif

/**
 * @def dpl_highprec_mod
 * dpl_highprec_mod(a1, a2) - High Precision Modulus
 * Get the modulus a2 from a1, and store the result in a1.
 */
#ifndef dpl_highprec_mod
#   define dpl_highprec_mod(a1,a2)   (a1 = a1 % a2)
#endif


/**
 * @def DPL_HIGHPREC_VALUE_IS_ZERO(a)
 * Test if the specified high precision value is zero.
 */
#ifndef DPL_HIGHPREC_VALUE_IS_ZERO
#   define DPL_HIGHPREC_VALUE_IS_ZERO(a)     (a==0)
#endif


void dpl_time_val_normalize(dpl_time_val *t)
{
    DPL_CHECK_STACK();

    if (t->msec >= 1000) {
	t->sec += (t->msec / 1000);
	t->msec = (t->msec % 1000);
    }
    else if (t->msec <= -1000) {
	do {
	    t->sec--;
	    t->msec += 1000;
        } while (t->msec <= -1000);
    }

    if (t->sec >= 1 && t->msec < 0) {
	t->sec--;
	t->msec += 1000;

    } else if (t->sec < 0 && t->msec > 0) {
	t->sec++;
	t->msec -= 1000;
    }
}

static dpl_highprec_t get_elapsed( const dpl_timestamp *start,
                                  const dpl_timestamp *stop )
{
#if defined(DPL_HAS_INT64) && DPL_HAS_INT64!=0
    return u64tohighprec(stop->u64 - start->u64);
#else
    dpl_highprec_t elapsed_hi, elapsed_lo;

    elapsed_hi = stop->u32.hi - start->u32.hi;
    elapsed_lo = stop->u32.lo - start->u32.lo;

    /* elapsed_hi = elapsed_hi * U32MAX */
    dpl_highprec_mul(elapsed_hi, U32MAX);

    return elapsed_hi + elapsed_lo;
#endif
}

static dpl_highprec_t elapsed_usec( const dpl_timestamp *start,
                                   const dpl_timestamp *stop )
{
    dpl_timestamp ts_freq;
    dpl_highprec_t freq, elapsed;

    if (dpl_get_timestamp_freq(&ts_freq) != DPL_SUCCESS)
        return 0;

    /* Convert frequency timestamp */
#if defined(DPL_HAS_INT64) && DPL_HAS_INT64!=0
    freq = u64tohighprec(ts_freq.u64);
#else
    freq = ts_freq.u32.hi;
    dpl_highprec_mul(freq, U32MAX);
    freq += ts_freq.u32.lo;
#endif

    /* Avoid division by zero. */
    if (freq == 0) freq = 1;

    /* Get elapsed time in cycles. */
    elapsed = get_elapsed(start, stop);

    /* usec = elapsed * USEC / freq */
    dpl_highprec_mul(elapsed, USEC);
    dpl_highprec_div(elapsed, freq);

    return elapsed;
}

static dpl_highprec_t elapsed_msec( const dpl_timestamp *start,
                                   const dpl_timestamp *stop )
{
    dpl_timestamp ts_freq;
    dpl_highprec_t freq, elapsed;

    if (dpl_get_timestamp_freq(&ts_freq) != DPL_SUCCESS)
        return 0;

    /* Convert frequency timestamp */
#if defined(DPL_HAS_INT64) && DPL_HAS_INT64!=0
    freq = u64tohighprec(ts_freq.u64);
#else
    freq = ts_freq.u32.hi;
    dpl_highprec_mul(freq, U32MAX);
    freq += ts_freq.u32.lo;
#endif

    /* Avoid division by zero. */
    if (freq == 0) freq = 1;

    /* Get elapsed time in cycles. */
    elapsed = get_elapsed(start, stop);
dpl_i("%d %d %d %d",start->u32.lo,start->u32.hi,stop->u32.lo,stop->u32.hi);
    /* usec = elapsed * MSEC / freq */
    dpl_highprec_mul(elapsed, MSEC);
    dpl_highprec_div(elapsed, freq);

    return elapsed;
}

dpl_uint32_t dpl_elapsed_usec( const dpl_timestamp *start,
                                     const dpl_timestamp *stop )
{
    return (dpl_uint32_t)elapsed_usec(start, stop);
}
dpl_uint32_t dpl_elapsed_msec( const dpl_timestamp *start,
                                     const dpl_timestamp *stop )
{
    return (dpl_uint32_t)elapsed_msec(start, stop);
}


#if defined(DPL_HAS_UNISTD_H) && DPL_HAS_UNISTD_H != 0
#   include <unistd.h>

#   if defined(_POSIX_TIMERS) && _POSIX_TIMERS > 0 && \
       defined(_POSIX_MONOTONIC_CLOCK)
#       define USE_POSIX_TIMERS 1
#   endif

#endif

#if defined(DPL_HAS_PENTIUM) && DPL_HAS_PENTIUM!=0 && \
    defined(DPL_TIMESTAMP_USE_RDTSC) && DPL_TIMESTAMP_USE_RDTSC!=0 && \
    defined(DPL_M_I386) && DPL_M_I386!=0 && \
    defined(DPL_LINUX) && DPL_LINUX!=0
static int machine_speed_mhz;
static dpl_timestamp machine_speed;

static __inline__ unsigned long long int rdtsc()
{
    unsigned long long int x;
    __asm__ volatile (".byte 0x0f, 0x31" : "=A" (x));
    return x;
}

/* Determine machine's CPU MHz to get the counter's frequency.
 */
static int get_machine_speed_mhz()
{
    FILE *strm;
    char buf[512];
    int len;
    char *pos, *end;
	
    DPL_CHECK_STACK();
	
    /* Open /proc/cpuinfo and read the file */
    strm = fopen("/proc/cpuinfo", "r");
    if (!strm)
        return -1;
    len = fread(buf, 1, sizeof(buf), strm);
    fclose(strm);
    if (len < 1) {
        return -1;
    }
    buf[len] = '\0';

    /* Locate the MHz digit. */
    pos = strstr(buf, "cpu MHz");
    if (!pos)
        return -1;
    pos = strchr(pos, ':');
    if (!pos)
        return -1;
    end = (pos += 2);
    while (isdigit(*end)) ++end;
    *end = '\0';

    /* Return the Mhz part, and give it a +1. */
    return atoi(pos)+1;
}

dpl_status_t dpl_get_timestamp(dpl_timestamp *ts)
{
    if (machine_speed_mhz == 0) {
	machine_speed_mhz = get_machine_speed_mhz();
	if (machine_speed_mhz > 0) {
	    machine_speed.u64 = machine_speed_mhz * 1000000.0;
	}
    }
    
    if (machine_speed_mhz == -1) {
	ts->u64 = 0;
	return -1;
    } 
    ts->u64 = rdtsc();
    return 0;
}

dpl_status_t dpl_get_timestamp_freq(dpl_timestamp *freq)
{
    if (machine_speed_mhz == 0) {
	machine_speed_mhz = get_machine_speed_mhz();
	if (machine_speed_mhz > 0) {
	    machine_speed.u64 = machine_speed_mhz * 1000000.0;
	}
    }
    
    if (machine_speed_mhz == -1) {
	freq->u64 = 1;	/* return 1 to prevent division by zero in apps. */
	return -1;
    } 

    freq->u64 = machine_speed.u64;
    return 0;
}

#elif defined(DPL_DARWINOS) && DPL_DARWINOS != 0
#include <mach/mach.h>
#include <mach/clock.h>
#include <errno.h>

#define NSEC_PER_SEC	1000000000

dpl_status_t dpl_get_timestamp(dpl_timestamp *ts)
{
    mach_timespec_t tp;
    int ret;
    clock_serv_t serv;

    ret = host_get_clock_service(mach_host_self(), SYSTEM_CLOCK, &serv);
    if (ret != KERN_SUCCESS) {
	return DPL_RETURN_OS_ERROR(EINVAL);
    }

    ret = clock_get_time(serv, &tp);
    if (ret != KERN_SUCCESS) {
	return DPL_RETURN_OS_ERROR(EINVAL);
    }

    ts->u64 = tp.tv_sec;
    ts->u64 *= NSEC_PER_SEC;
    ts->u64 += tp.tv_nsec;

    return DPL_SUCCESS;
}

dpl_status_t dpl_get_timestamp_freq(dpl_timestamp *freq)
{
    freq->u32.hi = 0;
    freq->u32.lo = NSEC_PER_SEC;

    return DPL_SUCCESS;
}

#elif defined(USE_POSIX_TIMERS) && USE_POSIX_TIMERS != 0
#include <sys/time.h>
#include <time.h>
#include <errno.h>

#define NSEC_PER_SEC	1000000000

dpl_status_t dpl_get_timestamp(dpl_timestamp *ts)
{
    struct timespec tp;

    if (clock_gettime(CLOCK_MONOTONIC, &tp) != 0) {
	return -1;
    }

    ts->u64 = tp.tv_sec;
    ts->u64 *= NSEC_PER_SEC;
    ts->u64 += tp.tv_nsec;

    return DPL_SUCCESS;
}

dpl_status_t dpl_get_timestamp_freq(dpl_timestamp *freq)
{
    freq->u32.hi = 0;
    freq->u32.lo = NSEC_PER_SEC;

    return DPL_SUCCESS;
}

#else
#include <sys/time.h>
#include <errno.h>

#define USEC_PER_SEC	1000000

dpl_status_t dpl_get_timestamp(dpl_timestamp *ts)
{
    struct timeval tv;

    if (gettimeofday(&tv, NULL) != 0) {
	return -1;
    }

    ts->u64 = tv.tv_sec;
    ts->u64 *= USEC_PER_SEC;
    ts->u64 += tv.tv_usec;

    return DPL_SUCCESS;
}

dpl_status_t dpl_get_timestamp_freq(dpl_timestamp *freq)
{
    freq->u32.hi = 0;
    freq->u32.lo = USEC_PER_SEC;

    return DPL_SUCCESS;
}





#endif



dpl_time_val dpl_elapsed_time( const dpl_timestamp *start,
                                     const dpl_timestamp *stop )
{
    dpl_highprec_t elapsed = elapsed_msec(start, stop);
    dpl_time_val tv_elapsed;

    if (DPL_HIGHPREC_VALUE_IS_ZERO(elapsed)) {
        tv_elapsed.sec = tv_elapsed.msec = 0;
        return tv_elapsed;
    } else {
        dpl_highprec_t sec, msec;

        sec = elapsed;
        dpl_highprec_div(sec, MSEC);
        tv_elapsed.sec = (long)sec;

        msec = elapsed;
        dpl_highprec_mod(msec, MSEC);
        tv_elapsed.msec = (long)msec;

        return tv_elapsed;
    }
}

dpl_status_t dpl_gettickcount(dpl_time_val *tv)
{
    dpl_timestamp ts, start;
    dpl_status_t status;

    if ((status = dpl_get_timestamp(&ts)) != DPL_SUCCESS)
        return status;

    dpl_set_timestamp32(&start, 0, 0);
    *tv = dpl_elapsed_time(&start, &ts);

    return DPL_SUCCESS;
}

